
package cn.zlg.common.datadump.executor;

import cn.zlg.common.datadump.annotation.DataDumpEntity;
import cn.zlg.common.datadump.annotation.DataDumpField;
import cn.zlg.common.datadump.condition.QueryCondtionWrapper;
import cn.zlg.common.datadump.condition.QueryFieldCondition;
import cn.zlg.common.datadump.condition.QueryFieldWithValue;
import cn.zlg.common.datadump.convertor.FieldValueConvert;
import cn.zlg.common.datadump.processor.BaseExportEntityProcessor;
import cn.zlg.common.datadump.util.QueryDslUtils;
import com.joysuch.wwyt.bp.enums.DataStateTypes;
import com.joysuch.wwyt.core.enums.DeleteFlag;
import com.joysuch.wwyt.risk.entity.dsl.QRiskPoint;
import com.joysuch.wwyt.risk.enums.RiskPointStatus;
import com.mysema.commons.lang.Assert;
import com.querydsl.core.Tuple;
import com.querydsl.core.types.Expression;
import com.querydsl.core.types.ExpressionUtils;
import com.querydsl.core.types.Path;
import com.querydsl.core.types.Predicate;
import com.querydsl.core.types.dsl.ComparableExpressionBase;
import com.querydsl.core.types.dsl.EntityPathBase;
import com.querydsl.core.types.dsl.SimpleExpression;
import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 使用JPAQueryFactor构建查询器
 */
@Service
public class JPAQueryExportExecutor implements ApplicationContextAware {

    private static final Logger logger = LoggerFactory.getLogger(JPAQueryExportExecutor.class);

    @Autowired
    private JPAQueryFactory factory;

    private ApplicationContext applicationContext;

    public List query(Class entity, QueryCondtionWrapper conditions) throws Exception {
        DataDumpEntity entityAnnotation = (DataDumpEntity) entity.getAnnotation(DataDumpEntity.class);
        Assert.notNull(entityAnnotation,
                String.format("类%s没有%s注解,无法解析", entity.getName(), DataDumpEntity.class.getSimpleName()));
        Class mainEntity = entityAnnotation.entity();
        List<FieldDescriptor> fieldDescriptors = findFieldDescriptors(entity, entityAnnotation);
        List<QueryGroup> queryGroups = splitToQueryGroups(mainEntity, fieldDescriptors, conditions);

        return doQuery(entity, fieldDescriptors, queryGroups, conditions, entityAnnotation);
    }

    private List doQuery(Class entity, List<FieldDescriptor> fieldDescriptors, List<QueryGroup> queryGroups,
                         QueryCondtionWrapper conditions, DataDumpEntity entityAnnotation) throws Exception {
        QueryGroup mainGroup = queryGroups.get(0);
        List list = queryGroupData(mainGroup, conditions, true);
        // System.err.println(mainGroup.entities + " load " + list.size() + "条数据");
        mainGroup.queryData = list;
        if (queryGroups.size() > 1) {
            // 其他属性关联实体的值
            for (int i = 1; i < queryGroups.size(); i++) {
                // 获取关联实体所有可能的值
                Set refData = extractPropertyValueFromEntities(mainGroup.queryData, queryGroups.get(i).mainEntityProperties);
                System.err.println(refData);
                queryGroups.get(i).queryData = queryRefGroupData(queryGroups.get(i), refData);
                System.err
                        .println(queryGroups.get(i).entities + " load " + queryGroups.get(i).queryData.size() + "条数据");
            }
        }
        // 生成目标对象
        List transformData = transformData(entity, fieldDescriptors, queryGroups);
        if (entityAnnotation.exportProcessors().length > 0) {
            transformData = postProcess(list, transformData, entityAnnotation);
        }
        return transformData;
    }

    private List postProcess(List<Tuple> list, List transformData, DataDumpEntity entityAnnotation) {
        Class<? extends BaseExportEntityProcessor>[] processors = entityAnnotation.exportProcessors();
        for (Class<? extends BaseExportEntityProcessor> p : processors) {
            BaseExportEntityProcessor processor = applicationContext.getBean(p);
            try {
                if (processor.supportBatch()) {
                    transformData = processor.batchProcessAfterTransformed(list, transformData, entityAnnotation);
                } else {
                    for (int i = 0; i < list.size(); i++) {
                        Object v = processor.processAfterTransformed(list.get(i), transformData.get(i), entityAnnotation);
                        transformData.set(i, v);
                    }
                }
            } catch (Exception e) {
                logger.error("数据转换异常", e);
                throw new RuntimeException("数据转换异常", e);
            }
        }

        return transformData;
    }

    private Set extractPropertyValueFromEntities(List<Tuple> queryData, List<SimpleExpression> mainEntityProperties) {
        Set set = new HashSet();
        for (Tuple t : queryData) {
            for (SimpleExpression s : mainEntityProperties) {
                Object data = t.get(s);
                if (data != null) {
                    set.add(data);
                }
            }
        }
        return set;
    }

    private List transformData(Class entity, List<FieldDescriptor> fieldDescriptors, List<QueryGroup> queryGroups) {
        QueryGroup mainGroup = queryGroups.get(0);
        List<FieldDescriptor> batchConvertorFields = fieldDescriptors.stream().filter(fd -> {
            Class<? extends FieldValueConvert>[] convertors = fd.annotation.exportConvertors();
            for (Class<? extends FieldValueConvert> c : convertors) {
                FieldValueConvert convertor = applicationContext.getBean(c);
                if (convertor.supportBatch()) {
                    return true;
                }
            }
            return false;
        }).collect(Collectors.toList());
        Map<FieldDescriptor, List<Object>> batchConvertValue = batchConvertValue(mainGroup.queryData, batchConvertorFields);
        List result = new ArrayList(mainGroup.queryData.size());
        for (int i = 0; i < mainGroup.queryData.size(); i++) {
            result.add(convertToTargetObject(entity, fieldDescriptors, mainGroup.queryData.get(i), i, batchConvertValue));
        }
        return result;
    }

    private Object convertToTargetObject(Class entity, List<FieldDescriptor> fieldDescriptors, Tuple d, int index, Map<FieldDescriptor, List<Object>> batchConvertValue) {
        try {
            Object data = entity.getDeclaredConstructor().newInstance();
            for (FieldDescriptor fd : fieldDescriptors) {
                List<Object> values = batchConvertValue.get(fd);
                Object transformedValue = null;
                if (values != null) {
                    transformedValue = values.get(index);

                } else {
                    if (fd.isMainEntityField() || fd.annotation.useJoin()) {
                        Object value = d.get(fd.annotation.useJoin() ? fd.refEntityPath : fd.entityPath); //
                        // 传递tuple的值
                        transformedValue = transformValue(d, value, fd.annotation);
                        // Object transformedValue = transformValue(
                        // d.get(fd.annotation.useJoin() ? fd.refEntityPath : fd.entityPath), value,
                        // fd.annotation);
                    } else {
                        // 从外链对象中取值
                        Object value = d.get(fd.entityPath);
                        Object targetValue = searchValueFromRefDataList(value, fd.belongToGroup.queryData,
                                fd.refEntityPath, fd.refEntityIdPath);
                        transformedValue = transformValue(d, targetValue, fd.annotation);
                    }
                }
                fd.field.setAccessible(true);
                fd.field.set(data, transformedValue);
            }
            return data;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private Map<FieldDescriptor, List<Object>> batchConvertValue(List<Tuple> queryData, List<FieldDescriptor> batchConvertorFields) {
        Map<FieldDescriptor, List<Object>> map = new HashMap<>(queryData.size(), 1.0f);
        for (FieldDescriptor fd : batchConvertorFields) {
            List data = new ArrayList();
            // 首先收集原始值
            for (Tuple t : queryData) {
                if (fd.isMainEntityField() || fd.annotation.useJoin()) {
                    Object value = t.get(fd.annotation.useJoin() ? fd.refEntityPath : fd.entityPath); //
                    data.add(value);
                } else {
                    // 从外链对象中取值
                    Object value = t.get(fd.entityPath);
                    Object targetValue = searchValueFromRefDataList(value, fd.belongToGroup.queryData,
                            fd.refEntityPath, fd.refEntityIdPath);
                    data.add(targetValue);
                }
            }
            // 依次处理
            Class<? extends FieldValueConvert>[] convertors = fd.annotation.exportConvertors();
            if (convertors != null && convertors.length > 0) {
                for (Class<? extends FieldValueConvert> convert : convertors) {
                    FieldValueConvert convertor = applicationContext.getBean(convert);
                    if (convertor.supportBatch()) {
                        // 支持批处理
                        data = convertor.convert(queryData, data, fd.annotation);
                    } else {
                        // 不支持批处理，依次处理
                        List temp = new ArrayList(data.size());
                        for (int i = 0; i < data.size(); i++) {
                            temp.add(convertor.convert(queryData.get(i), data.get(i), fd.annotation));
                        }
                        data = temp;
                    }
                }
            }
            map.put(fd, data);
        }
        return map;
    }

    private Object transformValue(Object bean, Object value, DataDumpField annotation) {
        Class<? extends FieldValueConvert>[] convertors = annotation.exportConvertors();
        if (convertors != null && convertors.length > 0) {
            Object transformedValue = value;
            for (Class<? extends FieldValueConvert> convert : convertors) {
                FieldValueConvert convertor = applicationContext.getBean(convert);
                transformedValue = convertor.convert(bean, transformedValue, annotation);
            }
            return transformedValue;
        }
        return value;
    }

    private Object searchValueFromRefDataList(Object value, List<Tuple> queryData, Path refEntityPath,
                                              Path refEntityIdPath) {
        for (Tuple t : queryData) {
            if (Objects.equals(value, t.get(refEntityIdPath))) {
                return t.get(refEntityPath);
            }
        }
        return null;
    }

    private List queryGroupData(QueryGroup group, QueryCondtionWrapper conditions, boolean isMainGroup)
            throws Exception {
        System.out.println(group.entities);
        JPAQuery<Tuple> query = null;
//        List<Predicate> leftJoinDeleteFlagPatches = new ArrayList<>();
        if (group.entities.size() == 1) {
            query = factory.select(group.properties.toArray(new Expression[0]))
                    .from(group.entities.toArray(new EntityPathBase[0]));// .where(conditions.getConditons())
        } else {
            query = factory.select(group.properties.toArray(new Expression[0])).from(group.entities.get(0));
            for (int i = 1; i < group.entities.size(); i++) {
                SimpleExpression[] joinConditionPair = group.joinConditionPaire.get(i - 1);
                query.leftJoin(group.entities.get(i)).on(joinConditionPair[0].eq(joinConditionPair[1]));
//                Predicate deleteFlagCondition = findDeleteFlagCondition(group.entities.get(i));
//                if(deleteFlagCondition != null) {
//                    leftJoinDeleteFlagPatches.add(deleteFlagCondition);
//                }
            }
        }

        if (isMainGroup && conditions.getPredicate() != null) {
            // mainGroup且有预编译的条件时，优先使用预编译的条件
            query.where(conditions.getPredicate());
        } else {
            // 应用过滤条件
            appendFilterConditions(query, group);
        }

        if (isMainGroup && conditions != null && conditions.getPageable() != null) {
            query.limit(conditions.getPageable().getPageSize()).offset(conditions.getPageable().getOffset());
        }
        if (isMainGroup && conditions != null && conditions.getOrders() != null) {
            query.orderBy(conditions.getOrders());
        }
//        if(leftJoinDeleteFlagPatches.size() > 0) {
//            query.where(leftJoinDeleteFlagPatches.toArray(new Predicate[0]));
//        }
        System.err.println(query);
        return query.fetch();
    }

    private Predicate findDeleteFlagCondition(EntityPathBase entityPathBase) {
        try {
            SimpleExpression deleteFlag = (SimpleExpression) QueryDslUtils.findQPath(entityPathBase, "deleteFlag");
            if (deleteFlag != null) {
                return ExpressionUtils.or(deleteFlag.eq(DeleteFlag.EFFECTIVE.getCode()), deleteFlag.isNull());
            }
        } catch (Exception e) {
        }
        return null;
    }

    /**
     * 查询属性关联的非join实体的值
     *
     * @param group
     * @param refKeyValues
     * @return
     * @throws Exception
     */
    private List queryRefGroupData(QueryGroup group, Collection refKeyValues)
            throws Exception {
        JPAQuery<Tuple> query = factory.select(group.properties.toArray(new Expression[0]))
                .from(group.entities.toArray(new EntityPathBase[0])).where(group.refEntityIdPath.in(refKeyValues));
        System.err.println("refQuery -> " + query);
        return query.fetch();
    }

    private void appendFilterConditions(JPAQuery<Tuple> query, QueryGroup group) throws Exception {
        Predicate p = null;
        for (QueryFieldWithValue c : group.queryConditions) {
            p = ExpressionUtils.and(p, createCondition(c));
        }
        for (SubQueryGroup sq : group.subQueryConditions) {
            // 先求子查询的值
            List<Tuple> data = queryGroupData(sq.subQuery, null, false);
            List list = new ArrayList();
            data.forEach(e -> {
                Object o = e.get(sq.subQuery.properties.get(0));
                if (o != null) {
                    list.add(o);
                }
            });
//            List list = data.stream().map(t -> t.get(sq.subQuery.properties.get(0))).collect(Collectors.toList());
            p = ExpressionUtils.and(p, sq.mainEntityProperty.in(list));
        }
        if (p != null) {
            query.where(p);
        }
    }

    private Predicate createCondition(QueryFieldWithValue c) throws Exception {
        if (c.getCondition() != null) {
            return c.getCondition().buildPredicate(c.getValue());
        } else {
            return c.getConditionSet().buildPredicate(c.getValue());
        }
    }

    private List<QueryGroup> splitToQueryGroups(Class mainEntity, List<FieldDescriptor> fieldDescriptors,
                                                QueryCondtionWrapper conditions) {
        List<QueryGroup> result = new ArrayList<>(5);
        List<FieldDescriptor> mainEntityFields = fieldDescriptors.stream()
                .filter(f -> f.refEntity == null || f.refEntity != null && f.annotation.useJoin())
                .collect(Collectors.toList());
        List<FieldDescriptor> refEntityFields = fieldDescriptors.stream()
                .filter(f -> f.refEntity != null && !f.annotation.useJoin()).collect(Collectors.toList());
        QueryGroup mainGroup = new QueryGroup();
        result.add(mainGroup);
        for (FieldDescriptor fd : mainEntityFields) {
            if (!mainGroup.entities.contains(fd.entity)) {
                mainGroup.entities.add(fd.entity);
            }
            mainGroup.properties.add(fd.entityPath);
            if (fd.refEntity != null) {
                if (!mainGroup.entities.contains(fd.refEntity)) {
                    mainGroup.entities.add(fd.refEntity);
                }

                mainGroup.properties.add(fd.refEntityPath);
                if (fd.annotation.useJoin()) {
                    SimpleExpression[] joinPair = new SimpleExpression[2];
                    joinPair[0] = (SimpleExpression) fd.entityPath;
                    joinPair[1] = (SimpleExpression) fd.refEntityIdPath;
                    mainGroup.joinConditionPaire.add(joinPair);
                }
            }
        }
        for (FieldDescriptor fd : refEntityFields) {
            // 将非join关联字段的属性也添加到选择列表中
            if (!mainGroup.properties.contains(fd.entityPath)) {
                mainGroup.properties.add(fd.entityPath);
            }
        }
        searchMainGroupQueryConditions(mainGroup, conditions == null ? null : conditions.getConditons());
        findRefQueryGroups(result, refEntityFields);
        return result;
    }

    private void searchMainGroupQueryConditions(QueryGroup mainGroup, List<QueryFieldWithValue> conditons) {
        if (conditons == null || conditons.isEmpty()) {
            return;
        }
        EntityPathBase mainEntity = mainGroup.entities.get(0);
        for (QueryFieldWithValue q : conditons) {
            if (q.getConditionSet() != null) {
                List<QueryFieldCondition> conditions = q.getConditionSet().getConditions();
                if (conditions != null && conditions.stream().allMatch(c -> c.getRefEntity().equals(mainEntity))) {
                    // 当前组合查询条件只支持主表，不满足条件的将忽略。关联其他表的情况太复杂，以后再实现
                    mainGroup.queryConditions.add(q);
                }
                continue;
            }
            boolean mainQueryCondition = false;
            if (q.getCondition().getRefEntity().equals(mainEntity)) {
                // 主表
                mainGroup.queryConditions.add(q);
                mainQueryCondition = true;
            } else if (mainGroup.entities.contains(q.getCondition().getRefEntity())) {
                // 包含在left join 实体中
                for (FieldDescriptor fd : mainGroup.descriptors) {
                    if (q.getCondition().getRefEntity().equals(fd.refEntity)
                            && fd.entityPath.equals(q.getCondition().getMainEntityProperty())) {
                        // 引用的是同一个entity，且和主entity关联属性一致. 例如人员有 所属部门，负责部门，关联实体都是QBpDepart, 但是关联字段不同，需要做区分
                        mainGroup.queryConditions.add(q);
                        mainQueryCondition = false;
                        break;
                    }
                }
            }
            if (!mainQueryCondition) {
                mainGroup.subQueryConditions.add(createSubQuery(q));
            }
        }
    }

    private SubQueryGroup createSubQuery(QueryFieldWithValue q) {
        SubQueryGroup group = new SubQueryGroup();
        group.mainEntityProperty = q.getCondition().getMainEntityProperty();
        group.subQuery = new QueryGroup();
        group.subQuery.entities.add(q.getCondition().getRefEntity());
        group.subQuery.properties.add(q.getCondition().getRefEntityKey());
        group.subQuery.queryConditions.add(q);
        return group;
    }

    private void findRefQueryGroups(List<QueryGroup> result, List<FieldDescriptor> refEntityFields) {
        if (refEntityFields.isEmpty()) {
            return;
        }
        for (FieldDescriptor fd : refEntityFields) {
            QueryGroup group = result.stream().filter(g -> {
                // 关联数据，只适用于单表查询，存在如下情况:
                // 用户表的 学历、健康程度、证件类型等均来自于字典表，将出现 3个 refEntity为字典的FieldDescriptor,
                // 这些数据可以合并一次查询，根据ID匹配对应实体, 可减少查询次数
                return g.descriptors.stream().anyMatch(d -> d.refEntity == fd.refEntity && d.refEntityIdPath == fd.refEntityIdPath);
            }).findFirst().orElse(null);
            if (group == null) {
                group = new QueryGroup();
                group.properties.add(fd.refEntityPath);
                group.properties.add(fd.refEntityIdPath);
                group.entities.add(fd.refEntity);
                group.mainEntityProperties.add((SimpleExpression) fd.entityPath);
                group.refEntityIdPath = (SimpleExpression) fd.refEntityIdPath;
                result.add(group);
            } else {
                if (!group.properties.contains(fd.refEntityPath)) {
                    group.properties.add(fd.refEntityPath);
                }
                if (!group.properties.contains(fd.refEntityIdPath)) {
                    group.properties.add(fd.refEntityIdPath);
                }
                if (!group.mainEntityProperties.contains(fd.entityPath)) {
                    group.mainEntityProperties.add((SimpleExpression) fd.entityPath);
                }
            }
            group.descriptors.add(fd);
            fd.belongToGroup = group;
        }
    }

    public static List<FieldDescriptor> findFieldDescriptors(Class entity, DataDumpEntity entityAnnotation) throws Exception {
        List<FieldDescriptor> list = new ArrayList<>();
        Field[] fields = entity.getDeclaredFields();
        for (Field f : fields) {
            DataDumpField dumpField = f.getAnnotation(DataDumpField.class);
            if (dumpField != null) {
                FieldDescriptor fd = new FieldDescriptor();
                fd.annotation = dumpField;
                fd.entity = (EntityPathBase) QueryDslUtils.findQEntityInstance(entityAnnotation.entity());
                fd.entityPath = QueryDslUtils.findQPath(entityAnnotation.entity(), dumpField.field());
                fd.field = f;
                fd.needSetOtherEntity = dumpField.needSetOtherEntity();
                if (dumpField.referenceEntity() != Object.class) {
                    fd.refEntity = (EntityPathBase) QueryDslUtils.findQEntityInstance(dumpField.referenceEntity());
                    fd.refEntityPath = QueryDslUtils.findQPath(dumpField.referenceEntity(), dumpField.referenceEntityField());
                    fd.refEntityIdPath = QueryDslUtils.findQPath(dumpField.referenceEntity(), dumpField.referenceEntityIdField());
                }
                list.add(fd);
            }
        }
        return list;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    static class QueryGroup {
        List<EntityPathBase> entities = new ArrayList<>(5);
        List<Expression> properties = new ArrayList<>();
        List<FieldDescriptor> descriptors = new ArrayList<>(5);
        // 左链接条件
        List<SimpleExpression[]> joinConditionPaire = new ArrayList<>(3);

        // 非链接表，采用子查询 in 的方式查询值
        List<SubQueryGroup> subQueryConditions = new ArrayList<>(3);

        // 主表及关联链接表的查询条件
        List<QueryFieldWithValue> queryConditions = new ArrayList<>();

        // 批量查询关联实体属性时，该属性对应的主表属性
        List<SimpleExpression> mainEntityProperties = new ArrayList<>(3);
        // 属性关联实体的ID属性
        SimpleExpression refEntityIdPath;

        List<Tuple> queryData;
    }

    static class SubQueryGroup {
        ComparableExpressionBase mainEntityProperty;
        QueryGroup subQuery;
    }

    public static class FieldDescriptor {
        EntityPathBase entity;
        Path entityPath;
        Field field;
        // 实体对象的反射字段
        PropertyDescriptor entityFieldPd;
        DataDumpField annotation;
        EntityPathBase refEntity;
        Path refEntityPath;
        Path refEntityIdPath;

        QueryGroup belongToGroup;

        boolean isMainEntityField() {
            return refEntity == null;
        }

        boolean needSetOtherEntity;

    }
}
